package com.net.shard;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.net.shard.domain.ShardConfig;
import com.net.shard.manager.ShardConfigManager;
import com.net.shard.manager.ShardSettingManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**

 */
public class ShardDataSourceConfigurer {
    private static final Logger LOGGER = LoggerFactory.getLogger(ShardDataSourceConfigurer.class);

    private ShardConfigManager shardConfigManager;
    private ShardSettingManager shardSettingManager;

    private String dataSourceName;
    private Integer minPoolSize;
    private Integer maxPoolSize;
    private Integer acquireIncrement;
    private Integer initialPoolSize;

    private Map<Long, DataSource> dataSourceMap = null; //缓存?

    private Map<Long, ShardConfig> shardConfigMap = null; //缓存?

    private int dataSourceCount = 0;  //缓存?

    public ShardConfigManager getShardConfigManager() {
        return shardConfigManager;
    }

    public void setShardConfigManager(ShardConfigManager shardConfigManager) {
        this.shardConfigManager = shardConfigManager;
    }

    public ShardSettingManager getShardSettingManager() {
        return shardSettingManager;
    }

    public void setShardSettingManager(ShardSettingManager shardSettingManager) {
        this.shardSettingManager = shardSettingManager;
    }

    public String getDataSourceName() {
        return dataSourceName;
    }

    public void setDataSourceName(String dataSourceName) {
        this.dataSourceName = dataSourceName;
    }

    public Integer getMinPoolSize() {
        return minPoolSize;
    }

    public void setMinPoolSize(Integer minPoolSize) {
        this.minPoolSize = minPoolSize;
    }

    public Integer getMaxPoolSize() {
        return maxPoolSize;
    }

    public void setMaxPoolSize(Integer maxPoolSize) {
        this.maxPoolSize = maxPoolSize;
    }

    public Integer getAcquireIncrement() {
        return acquireIncrement;
    }

    public void setAcquireIncrement(Integer acquireIncrement) {
        this.acquireIncrement = acquireIncrement;
    }

    public Integer getInitialPoolSize() {
        return initialPoolSize;
    }

    public void setInitialPoolSize(Integer initialPoolSize) {
        this.initialPoolSize = initialPoolSize;
    }

    /*
    synchronized: 防止多线程竞争
     */
    public synchronized Map<Long, DataSource> initDataSources() {
        if(dataSourceMap != null){
            LOGGER.warn("DataSource has been inited|" + dataSourceName);
            return dataSourceMap;
        }

        List<ShardConfig> shards = shardConfigManager.listShardConfig(dataSourceName);
        if (shards.isEmpty()) {
            throw new RuntimeException("database shard config is empty, need to be fixed|" + dataSourceName);
        }

        int shardCount = shardSettingManager.getShardCount(dataSourceName);
        if(shards.size() != shardCount){
            throw  new RuntimeException("DataSource shard length is not match|" + dataSourceName +"|" + shardCount + "|" + shards.size());
        }

        Map<Long, DataSource> dataSources = new ConcurrentHashMap<>();
        Map<Long, ShardConfig> shardConfigs = new ConcurrentHashMap<>();
        for (ShardConfig shard : shards) {
            addShard(dataSources, shard);
            shardConfigs.put(shard.getShardId(), shard);
        }

        dataSourceMap = dataSources;
        shardConfigMap = shardConfigs;
        dataSourceCount = shards.size();
        return dataSourceMap;
    }

    public synchronized Map<Long, ShardConfig> getShardConfigMap() {
        if(dataSourceMap == null){
            initDataSources();
        }
        return shardConfigMap;
    }

    public int getDataSourceCount() {
        return dataSourceCount;
    }

    private void addShard(Map<Long, DataSource> dataSources, ShardConfig shard) {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        try {
            dataSource.setDriverClass(shard.getDriverClass()); //loads the jdbc driver
        } catch (Throwable e) {
            throw new RuntimeException("hermes database shard init error|" + dataSourceName + "|" + shard.getId() + "|" + shard.getDriverClass(), e);
        }
        dataSource.setJdbcUrl(getJdbcUrl(shard));
        dataSource.setUser(shard.getUser());
        dataSource.setPassword(shard.getPassword());

        //和spring配置的数据源一样
        dataSource.setAcquireIncrement(getAcquireIncrement());
        dataSource.setInitialPoolSize(getInitialPoolSize());
        dataSource.setMaxIdleTime(600);
        dataSource.setMaxPoolSize(getMaxPoolSize());
        dataSource.setMinPoolSize(getMinPoolSize());
        dataSource.setAcquireRetryDelay(2000);
        dataSource.setAcquireRetryAttempts(300);
        dataSource.setBreakAfterAcquireFailure(false);
        dataSource.setTestConnectionOnCheckin(true);
        dataSource.setIdleConnectionTestPeriod(15);

        dataSources.put(shard.getShardId(), dataSource);
    }

    private String getJdbcUrl(ShardConfig shardConfig){
        StringBuilder builder = new StringBuilder();
        builder.append("jdbc:mysql://").append(shardConfig.getHost()).append(":").append(shardConfig.getPort());
        builder.append("/").append(shardConfig.getDbName());
        builder.append("?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedStatements=true&allowMultiQueries=true");
        return builder.toString();
    }

    public boolean needRefreshDataSource() {
        LOGGER.info("needRefreshDataSource check");
        int shardCount = shardSettingManager.getShardCount(dataSourceName);
        return shardCount != dataSourceCount;
    }

    public synchronized Map<Long, DataSource> refreshDataSources() {
        LOGGER.info("refreshDataSources start dataSourceCount={}", dataSourceCount);
        List<ShardConfig> shards = shardConfigManager.listShardConfig(dataSourceName);
        if (shards.isEmpty()) {
            throw new RuntimeException("database shard config is empty, need to be fixed|" + dataSourceName);
        }

        int shardCount = shardSettingManager.getShardCount(dataSourceName);
        if(shards.size() != shardCount){
            throw  new RuntimeException("DataSource shard length is not match|" + dataSourceName +"|" + shardCount + "|" + shards.size());
        }

        for (ShardConfig shard : shards) {
            //原来没有的
            if (!shardConfigMap.containsKey(shard.getShardId())) {
                addShard(dataSourceMap, shard);
                shardConfigMap.putIfAbsent(shard.getShardId(), shard);
                LOGGER.info("refreshDataSources add shardId={}", shard.getShardId());
            }
        }

        dataSourceCount = shards.size();
        LOGGER.info("refreshDataSources end dataSourceCount={}", dataSourceCount);
        return dataSourceMap;
    }
}
